package edu.unc.ils.nlp;

import java.io.File;

import java.io.FileWriter;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import maui.stemmers.Stemmer;

import org.jgrapht.DirectedGraph;
import org.jgrapht.GraphPath;
import org.jgrapht.alg.KShortestPaths;
import org.jgrapht.graph.DefaultDirectedGraph;
import org.jgrapht.graph.DefaultEdge;
import org.openrdf.concepts.skos.core.Concept;
import org.openrdf.elmo.ElmoModule;
import org.openrdf.elmo.sesame.SesameManager;
import org.openrdf.elmo.sesame.SesameManagerFactory;
import org.openrdf.repository.Repository;
import org.openrdf.repository.sail.SailRepository;
import org.openrdf.sail.nativerdf.NativeStore;

import edu.unc.ils.pos.POSFilter;


public class Vocabulary
{
    public static final int NO_TERM = 0;
    public static final int PREFERRED_TERM = 1;
    public static final int ALTERNATE_TERM = 2;
    Stemmer stemmer;
    POSFilter posFilter;
    String vocabularyName;
    String vocabularyPath;

    
    /* Map stemmed pref label to unstemmed pref labels */
	Map<String, Set<String>> stemmedPrefLabelMap = new HashMap<String, Set<String>>();
	
	/* Map of stemmed alt labels to unstemmed alt labels */
	Map<String, Set<String>> stemmedAltLabelMap = new HashMap<String, Set<String>>();
	
	/* Map of unstemmed alt labels to pref labels */
	Map<String, String> altToPrefMap = new HashMap<String, String>();
	
	Map<String, List<String>> relatedMap = new HashMap<String, List<String>>();
	Map<String, List<String>> narrowerMap = new HashMap<String, List<String>>();
	
	/* List of unstemmed pref labels */
	List<String> prefLabelList = new ArrayList<String>();
	
	DirectedGraph<String, DefaultEdge> graph = 
		new DefaultDirectedGraph<String, DefaultEdge>(DefaultEdge.class);
	
	public Vocabulary(String path)
	{
	    this.vocabularyName = new File(path).getName();
	    this.vocabularyPath = path;
	}
	
    // Build the graph and lookup tables
    public void init() throws Exception
    {
        NativeStore store;
        SesameManager manager;
        Repository repository;
        SesameManagerFactory factory;

        // Open the Sesame repository
        String indexes = "spoc,ospc";
        store = new NativeStore(new File(vocabularyPath), indexes);
        repository = new SailRepository(store);
        repository.initialize();
        ElmoModule module = new ElmoModule();
        factory = new SesameManagerFactory(module, repository);
        manager = factory.createElmoManager();
        
        // Add a root node, since most vocabularies do not have one.
        graph.addVertex(this.vocabularyName);
        
        // Add all concepts to the graph as vertices
        for (Concept concept : manager.findAll(Concept.class)) 
            graph.addVertex(concept.getSkosPrefLabel().toLowerCase());
        
        for (Concept concept:  manager.findAll(Concept.class)) 
        {
            String prefLabel = concept.getSkosPrefLabel();
            String filteredPrefLabel = prefLabel;
            if (posFilter != null)
                filteredPrefLabel = posFilter.applyFilter(prefLabel);
            String stemmedPrefLabel = stem(filteredPrefLabel);
            
            Set<String> prefLabels = stemmedPrefLabelMap.get(stemmedPrefLabel);
            if (prefLabels == null)
                prefLabels = new HashSet<String>();
            prefLabels.add(prefLabel);
            prefLabelList.add(prefLabel);
            stemmedPrefLabelMap.put(stemmedPrefLabel, prefLabels);
            
            Set<String> als = concept.getSkosAltLabels();
            for (String altLabel: als) {
                String filteredAltLabel = altLabel;
                if (posFilter != null)
                    filteredAltLabel = posFilter.applyFilter(altLabel);
                String stemmedAltLabel = stem(filteredAltLabel);

                Set<String> altLabels = stemmedAltLabelMap.get(stemmedAltLabel);
                if (altLabels == null)
                    altLabels = new HashSet<String>();
                altLabels.add(altLabel);
                stemmedAltLabelMap.put(stemmedAltLabel, altLabels);
                altToPrefMap.put(altLabel, prefLabel);
            }
            
            Set<Concept> broaders = concept.getSkosBroaders();
            if (broaders == null || broaders.size() == 0)
                graph.addEdge(this.vocabularyName, concept.getSkosPrefLabel().toLowerCase());

            
            Set<Concept> narrowers = concept.getSkosNarrowers();
            for (Concept narrower: narrowers) 
            {
                List<String> narrowerList = narrowerMap.get(prefLabel);
                if (narrowerList == null)
                    narrowerList = new ArrayList<String>();
                narrowerList.add(narrower.getSkosPrefLabel());
                narrowerMap.put(prefLabel, narrowerList);
                
                graph.addEdge(concept.getSkosPrefLabel().toLowerCase(), narrower.getSkosPrefLabel().toLowerCase());
            }
            
            Set<Concept> relateds = concept.getSkosRelated();
            for (Concept related: relateds) 
            {
                List<String> relatedList = relatedMap.get(prefLabel);
                if (relatedList == null)
                    relatedList = new ArrayList<String>();
                relatedList.add(related.getSkosPrefLabel());
                relatedMap.put(prefLabel, relatedList);
                //  graph.addEdge(concept.getSkosPrefLabel().toLowerCase(), related.getSkosPrefLabel().toLowerCase());
            }
        }
    }	
	
    public void setPOSFilter(POSFilter posFilter) {
        this.posFilter = posFilter;
    }
        
    public void setStemmer(Stemmer stemmer) {
        this.stemmer = stemmer;
    }
	    
    public String stem(String text) 
    {
        String[] words = text.split(" ");
        StringBuffer stemmedPhrase = new StringBuffer();
        for (int i=0; i<words.length; i++) {
            if (i > 0)
                stemmedPhrase.append(' ');
            String stemmedWord = stemmer.stem(words[i]);
            stemmedPhrase.append(stemmedWord);
        }
        return stemmedPhrase.toString();
    }    
    
	public int lookupNonStemmed(String nonStemmed, Map<String, String> found)
	{
	    int ret = NO_TERM;
	    if (prefLabelList.contains(nonStemmed))
	    {
	        found.put(nonStemmed, "");
	        ret = PREFERRED_TERM;
	    }
        else 
        {
            Set<String> altLabels = stemmedAltLabelMap.get(nonStemmed);
            if (altLabels != null)
            {
                for (String altLabel: altLabels) {
                    String prefLabel = altToPrefMap.get(altLabel);
                    found.put(prefLabel, "");
                }
                ret = ALTERNATE_TERM;
            }
        }	    
	    return ret;
	}
	
	public int lookup(String stemmed, Map<String, String> terms) 
	{
	    int ret = NO_TERM;
	    
		Set<String> found = stemmedPrefLabelMap.get(stemmed);
		if (found == null) 
		{
			Set<String> altLabels = stemmedAltLabelMap.get(stemmed);
			if (altLabels != null)
			{
				found = new HashSet<String>();
				for (String altLabel: altLabels) {
					String prefLabel = altToPrefMap.get(altLabel);
					found.add(prefLabel);
				}
				for (String f: found)
				    terms.put(f, "");
				ret = ALTERNATE_TERM;
			}
		}
		else
		{
            for (String f: found)
                terms.put(f, "");
		    ret = PREFERRED_TERM;
		}
		return ret;
	}
	
	public boolean isBroaderNarrower(String term1, String term2)
	{
	    boolean isBN = false;
	    List<String> narrowers1 = narrowerMap.get(term1);
	    if (narrowers1 != null)
	    {    
	        if (narrowers1.contains(term2))
	            isBN = true;
	        else
	        {
	            List<String> narrowers2 = narrowerMap.get(term2);
	            if (narrowers2 != null)
	            {
	                if (narrowers2.contains(term1))
	                    isBN = true;
	            }
	        }
	    }
	    return isBN;
	}
	
	public boolean isRelated(String term1, String term2)
	{
	    boolean isRelated = false;
        List<String> related1 = relatedMap.get(term1);
        if (related1 != null)
        {    
            if (related1.contains(term2))
                isRelated = true;
            else
            {
                List<String> related2 = relatedMap.get(term2);
                if (related2 != null)
                {
                    if (related2.contains(term1))
                        isRelated = true;
                }
            }
        }
        return isRelated;
	}
	
	/*
	// Given a list of phrases, return the subgraph
	public void getSubgraph(List<String> terms) 
	{
		DirectedGraph<String, DefaultEdge> subgraph = 
			new DefaultDirectedGraph<String, DefaultEdge>(DefaultEdge.class);
		
		KShortestPaths<String, DefaultEdge> ksp = new KShortestPaths<String, DefaultEdge>(graph, ROOT_NODE, 20);
        
        for (String term: terms) 
        	graph.addVertex(term);

        for (String term: terms) 
        {
        	// Get the paths to the root
        	List<GraphPath<String, DefaultEdge>> paths = ksp.getPaths(term);
        	
        	for (GraphPath<String, DefaultEdge> path: paths) {
        		List<DefaultEdge> edges = path.getEdgeList();
        		for (DefaultEdge edge: edges) 
        		{
        			String source = graph.getEdgeSource(edge);
        			String target = graph.getEdgeTarget(edge);
        			
        			if (!subgraph.containsVertex(source)) 
        				subgraph.addVertex(source);
        			
        			if (!subgraph.containsVertex(target)) 
        				subgraph.addVertex(target);	
        			
        			subgraph.addEdge(source, target);
        		}
        	}
        }
        
        String[] vertices = subgraph.vertexSet().toArray(new String[0]);
        Matrix matrix = new Matrix(terms.size(), vertices.length);
        for (int i=0; i<terms.size(); i++) {
        	String term = terms.get(i);
        	
        	for (int j=0; j<vertices.length; j++) 
        	{
        		String vertex = vertices[j];
        		if (term.equals(vertex))
        			continue;
        		
        		DefaultEdge edge = subgraph.getEdge(term, vertex);
        		if (edge != null) 
        			matrix.set(i, j, 1);
        		else
        			matrix.set(i, j, 0);
        		
        		subgraph.getAllE
        	}
        }  
	}
	*/
	

	
	public void writeGraphViz(String name, Set<String> terms, File outDir)  throws Exception
	{
        KShortestPaths<String, DefaultEdge> ksp = 
            new KShortestPaths<String, DefaultEdge>(graph, this.vocabularyName, 20);
        
        
        Map<String, Set<String>> relMap = new HashMap<String, Set<String>>();
        
        Set<String> allTerms = new HashSet<String>();
        
        for (String term: terms) 
        {
        	System.out.println("Getting paths for " + term);
        	// Get the paths to the root
        	List<GraphPath<String, DefaultEdge>> paths = null;
        	try
        	{	
        		paths = ksp.getPaths(term.toLowerCase());
        	} catch (IllegalArgumentException e) {
        		e.printStackTrace();
        		continue;
        	}
        	
        	for (GraphPath<String, DefaultEdge> path: paths) {
        		List<DefaultEdge> edges = path.getEdgeList();
        		for (DefaultEdge edge: edges) 
        		{
        			String source = graph.getEdgeSource(edge);
        			String target = graph.getEdgeTarget(edge);
        			Set<String> children = relMap.get(source);
        			if (children == null)
        				children = new HashSet<String>();
        			children.add(target);
        			relMap.put(source, children);
        			
        			allTerms.add(source);
        			allTerms.add(target);
        		}
        	}
        }
        

        FileWriter fw = new FileWriter(outDir + File.separator + name + ".dot");
        fw.write("digraph G { \n");
        fw.write("\tsize=\"10.25,7.75\";\n");
        fw.write("\torientation = landscape;\n");
        
        List<String> lowerTerms = new ArrayList<String>();
        for (String term: terms)
        {
        	lowerTerms.add(term.toLowerCase());
        }
        
        
        for (String rel: relMap.keySet())
        {
        	Set<String> narrowers = relMap.get(rel);
        	for (String narrower: narrowers) 
        	{
        		fw.write("\t\"" + rel + "\" -> \"" + narrower + "\";\n");
        		
        		if (lowerTerms.contains(narrower.toLowerCase()))
        			fw.write("\t\"" + narrower + "\" [style=bold,fontname=\"Times-Bold\"];\n");
        			
        	}
        }
        fw.write("}\n");
        fw.close();
	}
}
